<?php

// @formatter:off
/**
 * @file WCTag.php
 * @author Alejandro Dario Simi
 * @date $Date: 2014-06-03 03:40:32 +0000 (Tue, 03 Jun 2014) $
 *
 * $Id: WCTag.php 112 2014-06-03 03:40:32Z daemonraco@gmail.com $
 * $URL: http://wcomix.googlecode.com/svn/trunk/includes/representations/WCTag.php $
 */
// @formatter:on

/**
 * @class WCTag This class represents entries saved in the table "%tags".
 */
class WCTag extends WCRepresentationGroup implements WCPreviews, WCSearchIndexable, WCCountComics, WCCountImages, WCFlowContainer {
	/**
	 * @var string[] This variable holds the list of columns that cannot be
	 * directly modified by the pseudo-method __set().
	 * In this case tag_id and tag_group.
	 */
	protected $_PropertiesReadOnly = array(
		"id",
		"group"
	);
	/**
	 * @var string[] This variable holds the list of columns that must be
	 * treated as booleans and then send to the database as 'Y' and 'N' by
	 * the pseudo-method __set().
	 * In this case tag_valid_preview and tag_indexed.
	 */
	protected $_PropertiesBoolean = array(
		"indexed",
		"valid_flow",
		"valid_preview"
	);
	/**
	 * @var string This is the prefix of every column on the table
	 * '%tags'.
	 */
	protected $_PropertiesPrefix = "tag_";
	/**
	 * @var string This is the name of the represented table. In this case,
	 * '%tags'.
	 */
	protected $_PropertiesTable = "tags";
	/**
	 * @var boolean This flag indicates if this is a special tag.
	 */
	protected $_special = false;
	/**
	 * @var WCActiveTagHook This is a shortcut to an active tag hook used when
	 * this is an active tag.
	 */
	protected $_hook = false;
	/**
	 * @var WCGContainer This container holds all comics that are physically
	 * present in this tag.
	 */
	protected $_comics = null;
	/**
	 * @var WCGContainer This container holds all images that are physically
	 * present in this tag.
	 */
	protected $_images = null;
	protected $_invalidate = true;
	protected $_previews = array();
	protected $_previewsLoaded = false;
	//
	// Public methods.
	/**
	 * This method allows to set current tag to a comic.
	 *
	 * @param WCComic $comic Comic to be added as a member of this tag.
	 * @return boolean Returns true when the comic was successfully added.
	 */
	public function addComic(WCComic &$comic) {
		//
		// Loading global pointers.
		global $wcDefaults;
		//
		// Setting default value to be returned.
		$out = false;
		//
		// Checking that Read Only Mode is not activated.
		if(!$wcDefaults["read-only"]) {
			//
			// Checking special tags validations.
			$out = $this->specialChecks("CX", false);
			//
			// If there are no problems and the comic doesn't have the
			// current tag, it is added.
			if($out && !$this->hasComic($comic)) {
				//
				// Loading global pointers.
				global $wcUser;
				//
				// Setting default value to be returned.
				$out = false;
				//
				// Creating a statement to insert the new member.
				// @{
				$stmt = null;
				$stmtId = __CLASS__."::".__FUNCTION__;
				if($this->_db->has($stmtId)) {
					$stmt = $this->_db->get($stmtId);
				} else {
					$query = "insert\n";
					$query .= "        into {$this->_dbprefix}tag_comics (\n";
					$query .= "                mem_tag, mem_comic, mem_group, mem_tagger)\n";
					$query .= "        values (:id, :comic, :group, :user)";
					$stmt = $this->_db->prepare($stmtId, $query);
				}
				// @}
				//
				// Building database parameters.
				$params = array(
					":id" => $this->id(),
					":group" => $this->group,
					":comic" => $comic->id(),
					":user" => $wcUser->id()
				);
				//
				// Attempting to add the new member.
				if($stmt->execute($params)) {
					//
					// Invalidating comic search index.
					$comic->indexed(false);
					//
					// Invalidating view cache.
					if($this->invalidate()) {
						WCViewManager::I()->invalidate(WCViewManager::KeyComic, $comic->id());
						WCViewManager::I()->invalidate(WCViewManager::KeyTag, $this->id());
					}
					//
					// Tag previews are no longer valid.
					$this->setValidPreviews(false);
					//
					// Tag flow is no longer valid.
					$this->setValidFlow(false);
					//
					// Tag has a new member.
					$this->count++;
					//
					// Comic's container is not correct.
					$this->_comics = false;

					$this->reload();
					//
					// At this point, comic was successfully
					// added as member.
					$out = true;
				}
			}
		}
		//
		// Returning addition status.
		return $out;
	}
	/**
	 * This method allows to add all members of a directory to this tag.
	 *
	 * @param WCDirectory $directory Directory holding the items to be added.
	 * @return boolean Returns true when all members of the directory were added.
	 */
	public function addDirectory(WCDirectory &$directory) {
		//
		// Making special checks.
		$out = $this->specialChecks("DI", false);
		//
		// Checking if it's ok to add elements.
		if($out) {
			//
			// Invalidating view cache.
			$this->_invalidate = false;
			WCViewManager::I()->invalidate(WCViewManager::KeyComic, $directory->comics());
			WCViewManager::I()->invalidate(WCViewManager::KeyImage, $directory->images());
			WCViewManager::I()->invalidate(WCViewManager::KeyDirectory, $directory->id());
			WCViewManager::I()->invalidate(WCViewManager::KeyTag, $this->id());
			//
			// Adding all directoriy's comics.
			if($this->specialChecks("CX", false)) {
				foreach($directory->comicsFull() as $comic) {
					$this->addComic($comic);
				}
			}
			//
			// Adding all directoriy's images.
			if($this->specialChecks("IM", false)) {
				foreach($directory->imagesFull() as $image) {
					$this->addImage($image);
				}
			}
			$this->_invalidate = true;
		}
		//
		// Returns the addition result.
		return $out;
	}
	/**
	 * This method allows to set current tag to a image.
	 *
	 * @param WCImage $image Image to be added as a member of this tag.
	 * @return boolean Returns true when the image was successfully added.
	 */
	public function addImage(WCImage &$image) {
		//
		// Loading global pointers.
		global $wcDefaults;
		//
		// Setting default value to be returned.
		$out = false;
		//
		// Checking that Read Only Mode is not activated.
		if(!$wcDefaults["read-only"]) {
			//
			// Checking special tags validations.
			$out = $this->specialChecks("IM", false);
			//
			// If there are no problems and the image doesn't have the
			// current tag, it is added.
			if($out && !$this->hasImage($image)) {
				//
				// Loading global pointers.
				global $wcUser;
				//
				// Setting default value to be returned.
				$out = false;
				//
				// Creating a statement to insert the new member.
				// @{
				$stmt = null;
				$stmtId = __CLASS__."::".__FUNCTION__;
				if($this->_db->has($stmtId)) {
					$stmt = $this->_db->get($stmtId);
				} else {
					$query = "insert\n";
					$query .= "        into {$this->_dbprefix}tag_images (\n";
					$query .= "                mem_tag, mem_image, mem_group, mem_tagger)\n";
					$query .= "        values (:id, :image, :group, :user)";
					$stmt = $this->_db->prepare($stmtId, $query);
				}
				// @}
				//
				// Building database parameters.
				$params = array(
					":id" => $this->id,
					":group" => $this->group,
					":image" => $image->id,
					":user" => $wcUser->id
				);
				//
				// Attempting to add the new member.
				if($stmt->execute($params)) {
					//
					// Invalidating comic search index.
					$image->indexed(false);
					//
					// Invalidating view cache.
					if($this->invalidate()) {
						WCViewManager::I()->invalidate(WCViewManager::KeyImage, $image->id());
						WCViewManager::I()->invalidate(WCViewManager::KeyTag, $this->id());
					}
					//
					// Tag previews are no longer valid.
					$this->setValidPreviews(false);
					//
					// Tag flow is no longer valid.
					$this->setValidFlow(false);
					//
					// Tag has a new member.
					$this->icount++;
					//
					// Image's container is not correct.
					$this->_images = false;

					$this->reload();
					//
					// At this point, image was successfully
					// added as member.
					$out = true;
				}
			}
		}
		//
		// Returning addition status.
		return $out;
	}
	/**
	 * This method allows to add all members of a tag to this one.
	 *
	 * @param WCTag $tag Tag holding the items to be added.
	 * @return boolean Returns true when all members were added.
	 */
	public function addTag(WCTag &$tag) {
		//
		// Making special checks.
		$out = $this->specialChecks("TA", false);
		//
		// Checking if it's ok to add elements.
		if($out) {
			//
			// Invalidating view cache.
			$this->_invalidate = false;
			WCViewManager::I()->invalidate(WCViewManager::KeyComic, $tag->comics());
			WCViewManager::I()->invalidate(WCViewManager::KeyImage, $tag->images());
			WCViewManager::I()->invalidate(WCViewManager::KeyTag, array($tag->id(), $this->id()));
			//
			// Adding all tag's comics.
			if($this->specialChecks("CX", false)) {
				foreach($tag->comicsFull() as $comic) {
					$this->addComic($comic);
				}
			}
			//
			// Adding all tag's images.
			if($this->specialChecks("IM", false)) {
				foreach($tag->imagesFull() as $image) {
					$this->addImage($image);
				}
			}
			$this->_invalidate = true;
		}
		//
		// Returns the addition result.
		return $out;
	}
	/**
	 * This method allows to get the list of comic ids that belongs to this
	 * directory.
	 *
	 * @return int[] Returns a list of comics.
	 */
	public function comics() {
		//
		// Loading members list
		$this->loadMembers();
		//
		// If there are no issues it returns the list, otherwise, it
		// returns false.
		return $this->ok() ? $this->_comics->ids() : false;
	}
	/**
	 * This method allows to get the list of comics that belongs to this
	 * directory.
	 *
	 * @return WCComics[] Returns a list of comics.
	 */
	public function comicsFull() {
		//
		// Loading members list
		$this->loadMembers();
		//
		// If there are no issues on this object, the list of items of its
		// container is returned, otherwise, an empty array is returned.
		return $this->ok() ? $this->_comics->items() : array();
	}
	/**
	 * This method allows to know how many comics belong to this tag.
	 *
	 * @return int Returns the number of members.
	 */
	public function countComics() {
		return $this->count;
	}
	/**
	 * This method allows to know how many images belong to this tag.
	 *
	 * @return int Returns the number of members.
	 */
	public function countImages() {
		return $this->icount;
	}
	public function countItems() {
		return $this->countComics() + $this->countImages();
	}
	public function flow() {
		return WCFlowManager::I()->flowOf($this);
	}
	public function flowType() {
		return WCFlowManager::TypeTagFlow;
	}
	public function generateFlow() {
		$items = array();

		$aux = $this->comicsFull();
		uasort($aux, "WCComic::NameSort");
		foreach($aux as $item) {
			$items[] = $item;
		}
		$aux = $this->imagesFull();
		uasort($aux, "WCImage::NameSort");
		foreach($aux as $item) {
			$items[] = $item;
		}

		$this->setValidFlow(WCFlowManager::I()->registerFlow($this, $items));
	}
	/**
	 * @todo doc
	 *
	 * @return boolean Returns true if a previews list was generated.
	 */
	public function generatePreviews() {
		//
		// Setting a default value to be returned.
		$out = false;
		//
		// Invalidating view cache.
		WCViewManager::I()->invalidate(WCViewManager::KeyTag, $this->id());

		if(WCPreviewsManager::I()->createTagPreviews($this)) {
			//
			// At this point, previews where generated and
			// assigned, and they are valid.
			// @{
			$this->setValidPreviews(true);
			$out = true;
			// @}
			$this->_previewsLoaded = false;
		}
		//
		// This is a good opportunity to update item's counts.
		$this->updateStats();
		//
		// Returning generation results.
		return $out;
	}
	/**
	 * This method allows to know if certain comic belongs to this tag.
	 *
	 * @param WCComic $comic Comics to check.
	 * @return boolean Returns true when the requested comic has this tag.
	 */
	public function hasComic(WCComic &$comic) {
		//
		// Setting default value to be returned.
		$found = false;
		//
		// Building database parameters.
		$params = array(
			":group" => $this->group,
			":tag" => $this->id,
			":comic" => $comic->id
		);
		//
		// Creating a statement to search a specific member.
		// @{
		$stmt = null;
		$stmtId = __CLASS__."::".__FUNCTION__;
		if($this->_db->has($stmtId)) {
			$stmt = $this->_db->get($stmtId);
		} else {
			$query = "select  1\n";
			$query .= "from    {$this->_dbprefix}tag_comics\n";
			$query .= "where   mem_tag   = :tag\n";
			$query .= " and    mem_group = :group\n";
			$query .= " and    mem_comic = :comic";

			$stmt = $this->_db->prepare($stmtId, $query);
		}
		// @}
		//
		// Checking member association.
		if($stmt->execute($params) && $stmt->rowCount() > 0) {
			$found = true;
		}
		//
		// Returning search result.
		return $found;
	}
	/**
	 * This method allows to know if this tag has comics.
	 *
	 * @return boolean Returns true when it has at least one comic.
	 */
	public function hasComics() {
		return $this->count > 0;
	}
	/**
	 * This method allows to know if certain image belongs to this tag.
	 *
	 * @param WCImage $image Images to check.
	 * @return boolean Returns true when the requested image has this tag.
	 */
	public function hasImage(WCImage &$image) {
		//
		// Setting default value to be returned.
		$found = false;
		//
		// Building database parameters.
		$params = array(
			":group" => $this->group,
			":tag" => $this->id,
			":image" => $image->id
		);
		//
		// Creating a statement to search a specific member.
		// @{
		$stmt = null;
		$stmtId = __CLASS__."::".__FUNCTION__;
		if($this->_db->has($stmtId)) {
			$stmt = $this->_db->get($stmtId);
		} else {
			$query = "select  1\n";
			$query .= "from    {$this->_dbprefix}tag_images\n";
			$query .= "where   mem_tag   = :tag\n";
			$query .= " and    mem_group = :group\n";
			$query .= " and    mem_image = :image";

			$stmt = $this->_db->prepare($stmtId, $query);
		}
		// @}
		//
		// Checking member association.
		if($stmt->execute($params) && $stmt->rowCount() > 0) {
			$found = true;
		}
		//
		// Returning search result.
		return $found;
	}
	/**
	 * This method allows to know if this tags has images.
	 *
	 * @return boolean Returns true when it has at least one image.
	 */
	public function hasImages() {
		return $this->icount > 0;
	}
	public function hasItems() {
		return $this->hasComics() || $this->hasImages();
	}
	/**
	 * This method allows to know if this tag has a preview image.
	 *
	 * @return boolean Returns true when this tag has at least one preview.
	 */
	public function hasPreview() {
		return $this->hasPreviews();
	}
	/**
	 * This method allows to know if this tag has a list of preview images.
	 *
	 * @return boolean Returns true when this tag has at least one preview.
	 */
	public function hasPreviews() {
		$this->loadPreviews();

		return count($this->_previews) > 0;
	}
	public function hasValidFlow() {
		return $this->valid_flow;
	}
	public function hasValidPreviews() {
		return $this->valid_preview;
	}
	public function hook() {
		//
		// Checking if a hook has to be added in shortcuts.
		if(!$this->_hook && $this->_special) {
			//
			// Loading global pointers.
			global $wcActiveTagsHolder;
			//
			// Loading group pointer.
			$this->group();
			//
			// Adding active tag hook shortcut.
			$this->_hook = $wcActiveTagsHolder->hook($this->name, $this->_group);
		}
		return $this->_hook;
	}
	/**
	 * This method allows to get the list of image ids that belongs to this
	 * directory.
	 *
	 * @return int[] Returns a list of images.
	 */
	public function images() {
		//
		// Loading members list
		$this->loadMembers();
		//
		// If there are no issues it returns the list, otherwise, it
		// returns false.
		return $this->ok() ? $this->_images->ids() : false;
	}
	/**
	 * This method allows to get the list of images that belongs to this
	 * directory.
	 *
	 * @return WCImages[] Returns a list of images.
	 */
	public function imagesFull() {
		//
		// Loading members list
		$this->loadMembers();
		//
		// If there are no issues on this object, the list of items of its
		// container is returned, otherwise, an empty array is returned.
		return $this->ok() ? $this->_images->items() : array();
	}
	/**
	 * This method allows to know whether an item is indexed or not. Also it
	 * allows to change such status.
	 *
	 * @param boolean $value New status to be set. When absent, this method
	 * works as a getter.
	 * @return boolean Returns true when the object is indexed.
	 */
	public function indexed($value = null) {
		if(!is_null($value)) {
			$this->indexed = $value;
		}

		return $this->indexed;
	}
	/**
	 * This method allows to know if this is a special tag.
	 *
	 * @return boolean Returns true when this is a special tag.
	 */
	public function isSpecial() {
		return $this->_special;
	}
	public function load($id) {
		//
		// Setting a default status before loading.
		$this->_ok = false;
		//
		// Setting a default special-tag status before loading.
		$this->_special = false;
		//
		// Removing active tag hook before loading.
		$this->_hook = false;
		//
		// Calling parent's method.
		parent::load($id);
		//
		// Checking if parent class loaded without problems.
		if($this->ok()) {
			//
			// Loading global pointers.
			global $wcActiveTagsHolder;
			//
			// Cleaning containers.
			// @{
			if($this->_comics !== null) {
				$this->_comics = null;
			}
			if($this->_images !== null) {
				$this->_images = null;
			}
			// @}
			//
			// Ensuring containers existence.
			$this->createContainers();
			//
			// Checking if it's a special tag.
			$this->_special = $wcActiveTagsHolder->isActive($this->name);
		}
		//
		// Returns the final status of this object.
		return $this->ok();
	}
	/**
	 * This method allows to get this tag's name.
	 *
	 * @return string Returns a name.
	 */
	public function name() {
		return $this->name;
	}
	/**
	 * This method allows to get the thumbnail associated with current tag.
	 *
	 * @return string Returns an image URIs.
	 */
	public function preview() {
		if($this->hasPreview()) {
			$list = $this->_previews;
			return array_shift($list);
		} else {
			global $wcDefaults;
			return $wcDefaults["preview"];
		}
	}
	/**
	 * This method allows to get the list of thumbnails associated with
	 * current tag.
	 *
	 * @return string[] Returns a list of image URIs.
	 */
	public function previews() {
		$this->loadPreviews();

		return $this->_previews;
	}
	public function reload() {
		$this->_special = false;
		$this->_hook = false;
		$this->_comics = null;
		$this->_images = null;

		return parent::reload();
	}
	/**
	 * This method allows to remove current tag.
	 *
	 * @return boolean Returns true on success.
	 */
	public function remove() {
		//
		// Removing every comic association with this tag.
		foreach($this->comicsFull() as $comic) {
			$this->removeComic($comic);
		}
		//
		// Removing every image association with this tag.
		foreach($this->imagesFull() as $image) {
			$this->removeImage($image);
		}
		//
		// Calling parent remover.
		return parent::remove();
	}
	/**
	 * This method allows to remove all members of the current tag.
	 *
	 * @return boolean Returns true when the tag was successfully emptied.
	 */
	public function removeAll() {
		//
		// Loading global pointers.
		global $wcDefaults;
		//
		// Setting default value to be returned.
		$out = false;
		//
		// Checking that Read Only Mode is not activated.
		if(!$wcDefaults["read-only"]) {
			//
			// Checking special tags validations.
			$out = $this->specialChecks("ANY", true);
			//
			// If everything is ok, the comic is removed as member of
			// the tag.
			if($out) {
				//
				// Setting default value to be returned.
				$out = false;
				//
				// Invalidating view cache.
				WCViewManager::I()->invalidate(WCViewManager::KeyTag, $this->id());
				//
				// Building database parameters.
				$params = array(
					":tag" => $this->id,
					":group" => $this->group
				);
				//
				// Creating a statement to remove all comics.
				// @{
				$stmt = null;
				$stmtId = __CLASS__."::".__FUNCTION__."[cmc]";
				if($this->_db->has($stmtId)) {
					$stmt = $this->_db->get($stmtId);
				} else {
					$query = "delete from {$this->_dbprefix}tag_comics\n";
					$query .= "where       mem_group = :group\n";
					$query .= " and        mem_tag   = :tag";
					$stmt = $this->_db->prepare($stmtId, $query);
				}
				// @}
				//
				// Attempting to remove all comics.
				if($stmt->execute($params)) {
					//
					// Tag lost all comics.
					$this->count = 0;
				}
				//
				// Creating a statement to remove all images.
				// @{
				$stmt = null;
				$stmtId = __CLASS__."::".__FUNCTION__."[img]";
				if($this->_db->has($stmtId)) {
					$stmt = $this->_db->get($stmtId);
				} else {
					$query = "delete from {$this->_dbprefix}tag_images\n";
					$query .= "where       mem_group = :group\n";
					$query .= " and        mem_tag   = :tag";
					$stmt = $this->_db->prepare($stmtId, $query);
				}
				// @}
				//
				// Attempting to remove all images.
				if($stmt->execute($params)) {
					//
					// Tag lost all images.
					$this->icount = 0;
				}
				//
				// Tag previews is no longer valid.
				$this->setValidPreviews(false);
				//
				// Tag flow is no longer valid.
				$this->setValidFlow(false);

				$this->reload();
				//
				// If tag's emptied, it's okey.
				$out = !$this->count && !$this->icount;
			}
		}
		//
		// Returning removal status.
		return $out;
	}
	/**
	 * This method allows to remove current tag from a comic.
	 *
	 * @param WCComic $comic Comic to be removed as a member of this tag.
	 * @return boolean Returns true when the comic was successfully removed.
	 */
	public function removeComic(WCComic &$comic) {
		//
		// Loading global pointers.
		global $wcDefaults;
		//
		// Setting default value to be returned.
		$out = false;
		//
		// Checking that Read Only Mode is not activated.
		if(!$wcDefaults["read-only"]) {
			//
			// Checking special tags validations.
			$out = $this->specialChecks("CX", true);
			//
			// If everything is ok, the comic is removed as member of
			// the tag.
			if($out) {
				//
				// Setting default value to be returned.
				$out = false;
				//
				// Creating a statement to remove the member.
				// @{
				$stmt = null;
				$stmtId = __CLASS__."::".__FUNCTION__;
				if($this->_db->has($stmtId)) {
					$stmt = $this->_db->get($stmtId);
				} else {
					$query = "delete from {$this->_dbprefix}tag_comics\n";
					$query .= "where       mem_tag   = :tag\n";
					$query .= " and        mem_group = :group\n";
					$query .= " and        mem_comic = :comic";
					$stmt = $this->_db->prepare($stmtId, $query);
				}
				// @}
				//
				// Building database parameters.
				$params = array(
					":tag" => $this->id(),
					":group" => $this->group,
					":comic" => $comic->id()
				);
				//
				// Attempting to add the new member.
				if($stmt->execute($params) && $stmt->rowCount() > 0) {
					//
					// Invalidating view cache.
					WCViewManager::I()->invalidate(WCViewManager::KeyComic, $comic->id());
					WCViewManager::I()->invalidate(WCViewManager::KeyTag, $this->id());
					//
					// Tag previews are no longer valid.
					$this->setValidPreviews(false);
					//
					// Tag flow is no longer valid.
					$this->setValidFlow(false);
					//
					// Tag lost a member.
					$this->count--;
					//
					// Removing comic from its container.
					$this->_comics->removeId($comic->id());

					$this->reload();
					//
					// At this point, comic was successfully
					// removed as member.
					$out = true;
				}
			}
		}
		//
		// Returning removal status.
		return $out;
	}
	/**
	 * This method allows to remove every comic inside a directory from this
	 * tag.
	 *
	 * @param WCDirectory $directory Directory to walk over.
	 * @return boolean Returns true when directory members were removed.
	 */
	public function removeDirectory(WCDirectory &$directory) {
		//
		// Performing special tag checks.
		$out = $this->specialChecks("DI", true);
		//
		// Checking that it's possible to remove items.
		if($out) {
			//
			// Invalidating view cache.
			$this->_invalidate = false;
			WCViewManager::I()->invalidate(WCViewManager::KeyComic, $directory->comics());
			WCViewManager::I()->invalidate(WCViewManager::KeyImage, $directory->images());
			WCViewManager::I()->invalidate(WCViewManager::KeyDirectory, $directory->id());
			WCViewManager::I()->invalidate(WCViewManager::KeyTag, $this->id());
			//
			// Removing each directory comic from this tag.
			foreach($directory->comicsFull() as $comic) {
				$this->removeComic($comic);
			}
			//
			// Removing each directory image from this tag.
			foreach($directory->imagesFull() as $image) {
				$this->removeImage($image);
			}
			$this->_invalidate = true;
		}
		//
		// Returning removal result.
		return $out;
	}
	/**
	 * This method allows to remove current tag from a image.
	 *
	 * @param WCImage $image Image to be removed as a member of this tag.
	 * @return boolean Returns true when the image was successfully removed.
	 */
	public function removeImage(WCImage &$image) {
		//
		// Loading global pointers.
		global $wcDefaults;
		//
		// Setting default value to be returned.
		$out = false;
		//
		// Checking that Read Only Mode is not activated.
		if(!$wcDefaults["read-only"]) {
			//
			// Checking special tags validations.
			$out = $this->specialChecks("IM", true);
			//
			// If everything is ok, the image is removed as member of
			// the tag.
			if($out) {
				//
				// Setting default value to be returned.
				$out = false;
				//
				// Creating a statement to remove the member.
				// @{
				$stmt = null;
				$stmtId = __CLASS__."::".__FUNCTION__;
				if($this->_db->has($stmtId)) {
					$stmt = $this->_db->get($stmtId);
				} else {
					$query = "delete from {$this->_dbprefix}tag_images\n";
					$query .= "where       mem_tag   = :tag\n";
					$query .= " and        mem_group = :group\n";
					$query .= " and        mem_image = :image";
					$stmt = $this->_db->prepare($stmtId, $query);
				}
				// @}
				//
				// Building database parameters.
				$params = array(
					":tag" => $this->id(),
					":group" => $this->group,
					":image" => $image->id()
				);
				//
				// Attempting to add the new member.
				if($stmt->execute($params) && $stmt->rowCount() > 0) {
					//
					// Invalidating view cache.
					WCViewManager::I()->invalidate(WCViewManager::KeyImage, $image->id());
					WCViewManager::I()->invalidate(WCViewManager::KeyTag, $this->id());
					//
					// Tag previews are no longer valid.
					$this->setValidPreviews(false);
					//
					// Tag flow is no longer valid.
					$this->setValidFlow(false);
					//
					// Tag lost a member.
					$this->icount--;
					//
					// Removing image from its container.
					$this->_images->removeId($image->id());

					$this->reload();
					//
					// At this point, image was successfully
					// removed as member.
					$out = true;
				}
			}
		}
		//
		// Returning removal status.
		return $out;
	}
	/**
	 * This method allows to remove every comic inside a tag from this
	 * tag.
	 *
	 * @param WCTag $tag Tag to walk over.
	 * @return boolean Returns true when tag members were removed.
	 */
	public function removeTag(WCTag &$tag) {
		//
		// Performing special tag checks.
		$out = $this->specialChecks("TA", true);
		//
		// Checking that it's possible to remove items.
		if($out) {
			//
			// Invalidating view cache.
			$this->_invalidate = false;
			WCViewManager::I()->invalidate(WCViewManager::KeyComic, $tag->comics());
			WCViewManager::I()->invalidate(WCViewManager::KeyImage, $tag->images());
			WCViewManager::I()->invalidate(WCViewManager::KeyTag, array($tag->id(), $this->id()));
			//
			// Removing each tag comic from this tag.
			foreach($tag->comicsFull() as $comic) {
				$this->removeComic($comic);
			}
			//
			// Removing each tag image from this tag.
			foreach($tag->imagesFull() as $image) {
				$this->removeImage($image);
			}
			$this->_invalidate = false;
		}
		//
		// Returning removal result.
		return $out;
	}
	/**
	 * This method allows to get a list of phrases that must be associated with an
	 * item.
	 *
	 * @return string[] Returns a list of phrases.
	 */
	public function words() {
		return WCSearch::StringToPatterns($this->name());
	}
	//
	// Protected methods.
	/**
	 * The method creates all required containers.
	 */
	protected function createContainers() {
		//
		// Checking comics container.
		if(!$this->_comics) {
			//
			// Creating a list for comics.
			$this->_comics = new WCContainer(WCComicsHolder::I());
		}
		//
		// Checking comics container.
		if(!$this->_images) {
			//
			// Creating a list for comics.
			$this->_images = new WCContainer(WCImagesHolder::I());
		}
	}
	protected function invalidate() {
		return $this->_invalidate;
	}
	/**
	 * This method loads the list of comics and images belonging to this tag.
	 * @note Sub-directories' comics are not considered as members.
	 */
	protected function loadMembers() {
		//
		// Ensuring containers existence.
		$this->createContainers();
		//
		// Checking that this directory is ok and the comics list is
		// not loaded yet.
		if($this->ok() && !$this->_comics->count()) {
			//
			// Clearing any previews load information.
			$this->_comics->reset();
			//
			// Creating a statement request members of this tag.
			// @{
			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__."[comics]";
			if($this->_db->has($stmtId)) {
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "select  *\n";
				$query .= "from    {$this->_dbprefix}tag_comics\n";
				$query .= "where   mem_tag   = :tag\n";
				$query .= " and    mem_group = :group\n";

				$stmt = $this->_db->prepare($stmtId, $query);
			}
			// @}
			//
			// Building database parameters.
			$params = array(
				":group" => $this->group,
				":tag" => $this->id
			);
			if($stmt->execute($params)) {
				//
				// Adding found comics to the list.
				foreach($stmt->fetchAll() as $row) {
					$this->_comics->addId($row["mem_comic"], $row);
				}
			}
			// @}
		}
		//
		// Checking that this directory is ok and the images list is
		// not loaded yet.
		if($this->ok() && !$this->_images->count()) {
			//
			// Clearing any previews load information.
			$this->_images->reset();
			//
			// Creating a statement request members of this tag.
			// @{
			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__."[images]";
			if($this->_db->has($stmtId)) {
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "select  *\n";
				$query .= "from    {$this->_dbprefix}tag_images\n";
				$query .= "where   mem_tag   = :tag\n";
				$query .= " and    mem_group = :group\n";

				$stmt = $this->_db->prepare($stmtId, $query);
			}
			// @}
			//
			// Building database parameters.
			$params = array(
				":group" => $this->group,
				":tag" => $this->id
			);
			if($stmt->execute($params)) {
				//
				// Adding found comics to the list.
				foreach($stmt->fetchAll() as $row) {
					$this->_images->addId($row["mem_image"], $row);
				}
			}
			// @}
		}
	}
	protected function loadPreviews() {
		if(!$this->_previewsLoaded) {
			$this->_previews = WCPreviewsManager::I()->getPreviews($this->group(), WCPreviewsManager::TypeTag, $this->id);

			if(count($this->_previews) <= 0) {
				$this->setValidPreviews(false);
			} else {
				global $wcPaths;
				foreach($this->_previews as $key => $value) {
					$this->_previews[$key] = WCSanitizer::WComixPath("{$wcPaths["cache-directory-uri"]}/{$wcPaths["cache-thumbs-dirname"]}/{$value}");
				}
			}

			$this->_previewsLoaded = true;
		}
	}
	public function setValidFlow($valid = true) {
		$this->valid_flow = $valid;
	}
	public function setValidPreviews($valid = true) {
		$this->valid_preview = $valid;
	}
	/**
	 * This method performs checks to know if current tag operations are
	 * allowed.
	 *
	 * @param string $type Type of item being added/removed. Its values can be
	 * 'CL', 'CX', 'DI', 'IM', 'TA' or 'ALL' (only for removal).
	 * @param boolean $removal Indicates if this check is for a removal
	 * operation. Otherwise, addition.
	 * @return boolean Returns true when operations are allows.
	 */
	protected function specialChecks($type = "CX", $removal = false) {
		//
		// Setting default value to be returned.
		$out = true;
		//
		// Checking if current tag is a special one. If it's not an
		// special on, all its operations are allowed.
		if($this->_special) {
			//
			// Checking if there's a hook to analyze.
			if($this->hook()) {
				//
				// Removal and addition operations are different.
				if($removal) {
					//
					// Checking if removal operation is
					// allowed.
					$out = $this->hook()->{"canBeRemoved{$type}"}();
				} else {
					//
					// Checking if addition operation is
					// allowed.
					$out = $this->hook()->{"canBeAdded{$type}"}();
				}
			}
		}
		//
		// Returning checks result.
		return $out;
	}
	/**
	 * This is an internal method called by the pseudo-method __set()
	 *
	 * @param string $name Name of the parameter being modified.
	 * @param string $value Value to set to the property with name $name.
	 * @param string $realValue Value built by the pseudo-method __set().
	 * @return boolean Returns true when it is alright to modify the property.
	 */
	protected function subSet($name, &$value, &$realValue) {
		//
		// Invalidating view cache.
		WCViewManager::I()->invalidate(WCViewManager::KeyTag, $this->id());
		//
		// When its name name changes, it must be re-indexed.
		if($name == "name") {
			$this->indexed = false;
		}
		//
		// All modifications are allowed.
		return true;
	}
	protected function updateStats() {
		//
		// Creating a statement request members of this tag.
		// @{
		$stmt = null;
		$stmtId = __CLASS__."::".__FUNCTION__."[stats]";
		if($this->_db->has($stmtId)) {
			$stmt = $this->_db->get($stmtId);
		} else {
			$query = "update  {$this->_dbprefix}tags\n";
			$query .= "set     tag_count = (\n";
			$query .= "                select  count(*)\n";
			$query .= "                from    {$this->_dbprefix}tag_comics as c\n";
			$query .= "                                inner join {$this->_dbprefix}comics\n";
			$query .= "                                        on(c.mem_comic = cmc_id)\n";
			$query .= "                where   c.mem_tag = :tag\n";
			$query .= "                 and    cmc_lost  = 'N'\n";
			$query .= "        ),\n";
			$query .= "        tag_icount = (\n";
			$query .= "                select  count(*)\n";
			$query .= "                from    {$this->_dbprefix}tag_images as i\n";
			$query .= "                                inner join {$this->_dbprefix}images\n";
			$query .= "                                        on(i.mem_image = img_id)\n";
			$query .= "                where   i.mem_tag = :tag\n";
			$query .= "                 and    img_lost  = 'N'\n";
			$query .= "        )\n";
			$query .= "where   tag_id = :tag\n";

			$stmt = $this->_db->prepare($stmtId, $query);
		}
		// @}
		//
		// Building database parameters.
		$params = array(":tag" => $this->id);
		$stmt->execute($params);
		$stmt->closeCursor();
		//
		// Tag flow may no longer be valid.
		$this->setValidFlow(false);
	}
	//
	// Public Class Methods
	/**
	 * This is a sorting method based on action names and it can be used on
	 * usort and uasort functions.
	 *
	 * @param WCTag $a Left operand.
	 * @param WCTag $b Right operand.
	 * @return boolean Returns true when the id of $a is greater than
	 * $b's.
	 */
	public static function IdSort(c &$a, WCTag &$b) {
		return $a->id > $b->id;
	}
	/**
	 * This is a sorting method based on action names and it can be used on
	 * usort and uasort functions.
	 *
	 * @param WCTag $a Left operand.
	 * @param WCTag $b Right operand.
	 * @return boolean Returns true when the name of $a is greater than
	 * $b's.
	 */
	public static function NameSort(WCTag &$a, WCTag &$b) {
		return strtolower($a->name) > strtolower($b->name);
	}
	/**
	 * This is the general sorting method and it can be used on usort and
	 * uasort functions.
	 *
	 * @param WCTag $a Left operand.
	 * @param WCTag $b Right operand.
	 * @return boolean Returns true when $a goes after $b.
	 */
	public static function Sort(WCTag &$a, WCTag &$b) {
		return self::NameSort($a, $b);
	}
}
